メディア展開データの内訳を見る#

メディア展開データを対象に、2章で取り上げた内訳を見るための可視化手法を再度適用します。

これまでの学びを振り返り、知識の定着を図りましょう。

初期設定#

以降では、マンガ・アニメ・ゲームデータを可視化するための初期設定を行います。 紙幅の都合のため、書籍版と一部構成が異なることにご注意ください。

Import#

必要なライブラリをImportします。

Hide code cell content
# warningsモジュールのインポート
import warnings

# データ解析や機械学習のライブラリ使用時の警告を非表示にする目的で警告を無視
# 本書の文脈では、可視化の学習に議論を集中させるために選択した
# ただし、学習以外の場面で、警告を無視する設定は推奨しない
warnings.filterwarnings("ignore")
Hide code cell content
# itertoolsモジュールのインポート
# 様々なパターンのループを効率的に実行可能
import itertools

# pathlibモジュールのインポート
# ファイルシステムのパスを扱う
from pathlib import Path

# typingモジュールからListのインポート
# 型ヒントとして利用
from typing import Dict, List

# numpy:数値計算ライブラリのインポート
# npという名前で参照可能
import numpy as np

# pandas:データ解析ライブラリのインポート
# pdという名前で参照可能
import pandas as pd

# plotly.expressのインポート
# インタラクティブなグラフ作成のライブラリ
# pxという名前で参照可能
import plotly.express as px

# plotly.graph_objectsのインポート
# より詳細なグラフ作成機能を利用可能
# goという名前で参照可能
import plotly.graph_objects as go

# plotly.graph_objectsからFigureクラスのインポート
# 型ヒントの利用を主目的とする
from plotly.graph_objects import Figure

なお、型ヒントについてはこちらを参照ください。

定数#

本Notebookで用いる定数を定義します。 なお、Pythonにおける定数の扱いについては、こちらを参照ください。

Hide code cell content
# メディア展開データが保存されているディレクトリのパス
DIR_IN = Path("../../data/mix/input/")

# 分析結果の出力先ディレクトリのパス
DIR_OUT = DIR_IN.parent / "output" / Path.cwd().parts[-1] / "props"
Hide code cell content
# 読み込み対象ファイル名の定義

# アニメ各話と原作マンガの作者の対応関係に関するファイル
FN_AE_CRT = "mix_ae_crt.csv"

# マンガ各話とアニメ作品の対応関係に関するファイル
FN_CE_AC = "mix_ce_ac.csv"
Hide code cell content
# plotlyの描画設定の定義

# plotlyのグラフ描画用レンダラーの定義
# Jupyter Notebook環境のグラフ表示に適切なものを選択
RENDERER = "plotly_mimetype+notebook"
Hide code cell content
# 可視化に関する設定値を定義

# weekdayを曜日に変換するための辞書
WD2STR = {
    0: "月",
    1: "火",
    2: "水",
    3: "木",
    4: "金",
    5: "土",
    6: "日",
}
Hide code cell content
# 質的変数の描画用のカラースケールの定義

# Okabe and Ito (2008)基準のカラーパレット
# 色の識別性が高く、多様な色覚の人々にも見やすい色組み合わせ
# 参考URL: https://jfly.uni-koeln.de/color/#pallet
OKABE_ITO = [
    "#000000",  # 黒 (Black)
    "#E69F00",  # 橙 (Orange)
    "#56B4E9",  # 薄青 (Sky Blue)
    "#009E73",  # 青緑 (Bluish Green)
    "#F0E442",  # 黄色 (Yellow)
    "#0072B2",  # 青 (Blue)
    "#D55E00",  # 赤紫 (Vermilion)
    "#CC79A7",  # 紫 (Reddish Purple)
]

関数#

Hide code cell content
def show_fig(fig: Figure) -> None:
    """
    所定のレンダラーを用いてplotlyの図を表示
    Jupyter Bookなどの環境での正確な表示を目的とする

    Parameters
    ----------
    fig : Figure
        表示対象のplotly図

    Returns
    -------
    None
    """

    # 図の周囲の余白を設定
    # t: 上余白
    # l: 左余白
    # r: 右余白
    # b: 下余白
    fig.update_layout(margin=dict(t=25, l=25, r=25, b=25))

    # 所定のレンダラーで図を表示
    fig.show(renderer=RENDERER)
Hide code cell content
def add_years_to_df(
    df: pd.DataFrame, unit_years: int = 10, col_date: str = "date"
) -> pd.DataFrame:
    """
    データフレームにunit_years単位で区切った年数を示す新しい列を追加

    Parameters
    ----------
    df : pd.DataFrame
        入力データフレーム
    unit_years : int, optional
        年数を区切る単位、デフォルトは10
    col_date : str, optional
        日付を含むカラム名、デフォルトは "date"

    Returns
    -------
    pd.DataFrame
        新しい列が追加されたデータフレーム
    """

    # 入力データフレームをコピー
    df_new = df.copy()

    # unit_years単位で年数を区切り、新しい列として追加
    df_new["years"] = (
        pd.to_datetime(df_new[col_date]).dt.year // unit_years * unit_years
    )

    # 'years'列のデータ型を文字列に変更
    df_new["years"] = df_new["years"].astype(str)

    return df_new
Hide code cell content
def add_weekday_to_df(df: pd.DataFrame, col_date: str = "date") -> pd.DataFrame:
    """
    指定されたDataFrameに曜日の情報を追加する関数

    Parameters
    ----------
    df : pd.DataFrame
        曜日情報を追加する対象のDataFrame
    col_date : str, optional
        日付情報が含まれているカラムの名前、デフォルトは "date"

    Returns
    -------
    pd.DataFrame
        曜日情報が追加された新しいDataFrame
    """

    # 元のDataFrameをコピーして新しいDataFrameを作成
    df_new = df.copy()

    # 日付カラムを元に曜日の数値を計算して新しいカラムに追加
    df_new["weekday"] = pd.to_datetime(df_new[col_date]).dt.weekday

    # 数値の曜日を文字列に変換して新しいカラムに追加
    df_new["weekday_str"] = df_new["weekday"].apply(lambda x: WD2STR[x])

    return df_new
Hide code cell content
def create_mosaicplot(
    df: pd.DataFrame,
    x: str,
    y: str,
    color: str,
    width: str,
    text: str,
    color_discrete_sequence: List[str] = OKABE_ITO,
) -> go.Figure:
    """
    指定されたDataFrameを元にモザイクプロットを作成する関数

    Parameters
    ----------
    df : pd.DataFrame
        プロットに使用するデータが含まれるDataFrame
    x : str
        x軸に表示するデータのカラム名
    y : str
        y軸に表示するデータのカラム名
    color : str
        グループ分けの基準となるデータのカラム名
    width : str
        各バーの幅を表すデータのカラム名
    text : str
        各バーに表示するテキストのデータのカラム名
    color_discrete_sequence : List[str], optional
        使用する色のリスト デフォルトはOKABE_ITOのカラーパレット

    Returns
    -------
    go.Figure
        作成されたモザイクプロットのFigureオブジェクト
    """

    # 空のFigureオブジェクトを作成
    fig = go.Figure()

    # color列に登場するユニークな要素に対し、色をマッピング
    unique_keys = df[color].unique()
    color_map = {
        name: color for name, color in zip(unique_keys, color_discrete_sequence)
    }

    # color列のユニークな要素ごとにDataFrameをフィルタリング
    for i, name in enumerate(unique_keys):
        df_tmp = df[df[color] == name].reset_index(drop=True)
        # 幅をwidth列から抽出
        widths = df_tmp[width]

        # バーの位置を計算し、プロットに追加
        # 幅が変わるようxの値を調整
        fig.add_trace(
            go.Bar(
                name=name,
                x=df_tmp[width].cumsum() - widths,
                y=df_tmp[y],
                text=df_tmp[text],
                width=widths,
                offset=0,
                marker_color=color_map[name],
            )
        )

        # 最初の要素を用いて、X軸ラベルの設定値を作成
        if i == 0:
            # 各「棒」の中央に配置されるように座標を計算
            tickvals = df_tmp[width].cumsum() - df_tmp[width] / 2
            ticktext = df_tmp[x].unique()
            # x軸の表示範囲を決定するために利用
            x_max = df_tmp[width].sum()

    # x軸の目盛りの位置、テキスト、表示範囲を設定
    # 「棒」の太さの合計値を1としたとき、左右に0.1ずつ余白が残るように調整
    fig.update_xaxes(
        tickvals=tickvals, ticktext=ticktext, title=x, range=[-x_max * 0.1, x_max * 1.1]
    )

    # y軸のタイトルを設定
    fig.update_yaxes(title=y)

    # プロットのレイアウトを設定、凡例タイトルも指定
    fig.update_layout(barmode="stack", legend_title=color)

    return fig
Hide code cell content
def add_ratio_by_col(df: pd.DataFrame, col_val: str, col_by: str) -> pd.DataFrame:
    """
    指定された列に基づいて、値の比率を計算し新たな列として追加

    Parameters
    ----------
    df : pd.DataFrame
        比率を計算するためのデータフレーム
    col_val : str
        比率を計算するための値を含む列の名前
    col_by : str
        比率をグループ化する基準となる列の名前

    Returns
    -------
    pd.DataFrame
        元のデータフレームに比率を表す新しい列「ratio」を追加したデータフレーム
    """
    df_out = df.copy()

    # グループごとの合計値を格納するための新しい列名を生成
    col_sum = f"{col_by}_total"

    # 指定されたグループ列に基づいて値の合計を計算し、一時的なデータフレームを作成
    df_tmp = df_out.groupby(col_by)[col_val].sum().reset_index(name=col_sum)

    # 一時的なデータフレームを元のデータフレームにマージし、各グループの合計値を追加
    df_out = pd.merge(df_out, df_tmp, how="left", on=col_by)

    # 指定された値の列を、そのグループの合計値で割り、新しい列「ratio」にその比率を格納
    df_out["ratio"] = df_out[col_val] / df_out[col_sum]

    return df_out
Hide code cell content
def add_text_of_ratio(df: pd.DataFrame, col_ratio: str) -> pd.DataFrame:
    """
    指定された比率の列を文字列形式に変換し、新たな列として追加

    比率は小数点以下2桁までの形式で文字列に変換

    Parameters
    ----------
    df : pd.DataFrame
        比率の文字列化を行う元のデータフレーム
    col_ratio : str
        比率を含む列の名前

    Returns
    -------
    pd.DataFrame
        元のデータフレームに、比率を表す文字列を含む新しい列「text」を追加したデータフレーム
    """
    df_out = df.copy()
    # 指定された比率の列を小数点以下2桁の文字列形式に変換して、新しい列"text"に格納
    df_out["text"] = df_out[col_ratio].apply(lambda x: f"{x:.2f}")
    return df_out
Hide code cell content
def add_weekday(df: pd.DataFrame, col_date: str = "date") -> pd.DataFrame:
    """
    指定された日付列に基づいて、曜日情報を整数と文字列の形式で新たな列として追加

    Parameters
    ----------
    df : pd.DataFrame
        曜日情報を追加する元のデータフレーム
    col_date : str, optional
        日付情報を含む列の名前、デフォルトは'date'

    Returns
    -------
    pd.DataFrame
        元のデータフレームに、曜日情報を表す2つの新しい列「weekday」と
        「weekday_str」を追加したデータフレーム
    """
    df_new = df.copy()
    # 指定された日付列をdatetime型に変換し、曜日を整数で表す新しい列'weekday'を追加
    df_new["weekday"] = pd.to_datetime(df_new[col_date]).dt.weekday
    # 'weekday'列の整数値を曜日の文字列に変換し、新しい列'weekday_str'に追加
    # WD2STRは曜日の整数値をキー、曜日の名前を値とする辞書である必要がある
    df_new["weekday_str"] = df_new["weekday"].apply(lambda x: WD2STR[x])
    return df_new
Hide code cell content
def format_cols(df: pd.DataFrame, cols_rename: Dict[str, str]) -> pd.DataFrame:
    """
    指定されたカラムのみをデータフレームから抽出し、カラム名をリネームする関数

    Parameters
    ----------
    df : pd.DataFrame
        入力データフレーム
    cols_rename : Dict[str, str]
        リネームしたいカラム名のマッピング(元のカラム名: 新しいカラム名)

    Returns
    -------
    pd.DataFrame
        カラムが抽出・リネームされたデータフレーム
    """

    # 指定されたカラムのみを抽出し、リネーム
    df = df[cols_rename.keys()].rename(columns=cols_rename)

    return df
Hide code cell content
def save_df_to_csv(df: pd.DataFrame, dir_save: Path, fn_save: str) -> None:
    """
    DataFrameをCSVファイルとして指定されたディレクトリに保存する関数

    Parameters
    ----------
    df : pd.DataFrame
        保存対象となるDataFrame
    dir_save : Path
        出力先ディレクトリのパス
    fn_save : str
        保存するCSVファイルの名前(拡張子は含めない)
    """
    # 出力先ディレクトリが存在しない場合は作成
    dir_save.mkdir(parents=True, exist_ok=True)

    # 出力先のパスを作成
    p_save = dir_save / f"{fn_save}.csv"

    # DataFrameをCSVファイルとして保存する
    df.to_csv(p_save, index=False, encoding="utf-8-sig")

    # 保存完了のメッセージを表示する
    print(f"DataFrame is saved as '{p_save}'.")

可視化例#

まず、可視化対象となるデータを読み込みましょう。

Hide code cell content
# pandasのread_csv関数でCSVファイルの読み込み
df_ce_ac = pd.read_csv(DIR_IN / FN_CE_AC)

円グラフ#

メディア展開データの分布を見るでは、アニメ化された実績のあるマンガ作品とそうでない作品で、掲載位置やその推移に違いが生じるか分析しました。 では、そもそもどのくらいの割合のマンガ作品がアニメ化に至るのでしょうか?

本節では円グラフを用いて「四大少年誌で連載されたマンガ作品のうちアニメ化されたものは1割程度である」という仮説を確認します。 1割という数字に理論的な根拠はありません。 2章で取り上げたへびちか先生の経験則[みのる, 2022]から着想を得たに過ぎません。

円グラフPie Chart) とは、主に質的変数に対して、その内訳を 扇形の角度 で表した可視化手法でした。 扇形グラフ とも呼ばれます。 質的変数の内訳を可視化する際に最もよく用いられるグラフの一つです。 それぞれの要素が全体に対してどの程度占めるか直感的にわかりやすいという長所がありますが、 一方で要素同士の内訳の比較がしづらいという短所もあります。 詳細は7章に整理してありますので、復習に役立ててください。

Hide code cell content
# 最小の合計各話数数を設定
min_nce = 8

# df_ce_acデータフレームを日付とceidで昇順に並び替え、新しいインデックスを割り当て
df_ce_ac = df_ce_ac.sort_values(["date", "ceid"], ignore_index=True)

# ccidで重複を削除し、それぞれのccidについて最初に記録された行のみを保持
df_cc_ac = df_ce_ac.drop_duplicates("ccid", ignore_index=True)

# n_ceがmin_nce以上、かつfirst_date_ccの年が1990年以降の行のみを選択
df_cc_ac = df_cc_ac[
    (df_cc_ac["n_ce"] >= min_nce)
    & (pd.to_datetime(df_cc_ac["first_date_cc"]).dt.year >= 1990)
].reset_index(drop=True)

# "acid"列に欠損値がない場合、つまりアニメーションが存在する場合にTrueを設定し、
# 新しい列"is_animated"をデータフレームに追加
df_cc_ac["is_animated"] = ~df_cc_ac["acid"].isna()
Hide code cell content
# df_cc_acデータフレームをアニメ化されているかどうか(is_animated)でグループ化し、
# それぞれのグループにおけるユニークなマンガ作品(ccid)の数を計算
df_pie = df_cc_ac.groupby("is_animated")["ccid"].nunique().reset_index(name="n_cc")

# 得られたデータフレームの列名をより分かりやすい名称に変更
df_pie = df_pie.rename(columns={"is_animated": "アニメ化", "n_cc": "マンガ作品数"})
Hide code cell content
# 可視化対象のDataFrameを確認
df_pie.head()
アニメ化 マンガ作品数
0 False 1208
1 True 129
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_pie, DIR_OUT, "pie")
DataFrame is saved as '../../data/mix/output/09/props/pie.csv'.
Hide code cell source
# Plotly Expressを使用して、df_pieデータフレームから円グラフを作成
# 'マンガ作品数'を円グラフの各セクションの大きさに、'アニメ化'を各セクションの名前に使用
fig = px.pie(
    df_pie, values="マンガ作品数", names="アニメ化", color_discrete_sequence=OKABE_ITO
)

# レイアウトを更新して凡例のタイトルを'アニメ化'に設定
fig.update_layout(legend={"title": "アニメ化"})

# 作成した図を表示する関数を呼び出し
show_fig(fig)

上図は、四大週刊少年誌において1990年以降に連載を開始したマンガ作品のうち、アニメ化された実績のあるものの割合を示した円グラフです。 マンガ作品として8話以上連載した作品を対象としています。

「四大少年誌で連載されたマンガ作品のうちアニメ化されたものは1割程度である」という仮説とかなり近い数値が出て驚きました。 ただし、2章で何度も触れたようなデータの打切りの問題や、アニメデータにおける欠損の問題等あることはご注意ください。

では、マンガ雑誌別に集計するとどうなるのでしょうか?

Hide code cell content
# df_cc_acデータフレームをマンガ雑誌名とアニメ化されているかどうかでグループ化し、
# それぞれのグループにおけるユニークなマンガ作品(ccid)の数を計算
df_pie2 = (
    df_cc_ac.groupby(["mcname", "is_animated"])["ccid"]
    .nunique()
    .reset_index(name="n_cc")
)

# 得られたデータフレームの列名をより分かりやすい名称に変更
df_pie2 = df_pie2.rename(
    columns={
        "mcname": "マンガ雑誌名",
        "is_animated": "アニメ化",
        "n_cc": "マンガ作品数",
    }
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_pie2.head()
マンガ雑誌名 アニメ化 マンガ作品数
0 週刊少年サンデー False 255
1 週刊少年サンデー True 32
2 週刊少年ジャンプ False 288
3 週刊少年ジャンプ True 49
4 週刊少年チャンピオン False 381
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_pie2, DIR_OUT, "pie2")
DataFrame is saved as '../../data/mix/output/09/props/pie2.csv'.
Hide code cell source
# 'マンガ作品数'を円グラフの各セクションの大きさに、'アニメ化'を各セクションの名前に使用
# 'facet_col'によって、マンガ雑誌名ごとに異なる円グラフを生成
fig = px.pie(
    df_pie2,
    values="マンガ作品数",
    names="アニメ化",
    facet_col="マンガ雑誌名",
    color_discrete_sequence=OKABE_ITO,
)

# レイアウトを更新して凡例のタイトルを'アニメ化'に設定
fig.update_layout(legend={"title": "アニメ化"})

# 各円グラフのタイトル(マンガ雑誌名)を、"マンガ雑誌名=値"から"値"のみに短縮
# これにより、グラフ上の各マンガ雑誌名の表示が簡潔になる
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# 作成した図を表示
show_fig(fig)

上図は、四大週刊少年誌において連載されたマンガ作品のうち、アニメ化された実績のあるものの割合をマンガ雑誌ごとに示した円グラフです。 なお、1990年以降に連載を開始し、合計8話継続したマンガ作品を可視化対象としています。 マンガ雑誌ごとの傾向の違いが表れました。 ただし、円グラフの分母、つまりマンガ雑誌ごとの合計マンガ作品数が違うことに注意しましょう。

なお、円グラフを複数並べて表示するとき、その順序に意図がない場合は、内訳に関してソートすると見やすくなることがあります。 ここではマンガ雑誌別に、アニメ化実績のあるマンガ作品の割合を基準にソートすると良いでしょう。

Hide code cell content
# df_pie2データフレームのコピーを作成し、df_pie3として保存
df_pie3 = df_pie2.copy()

# 'マンガ雑誌名'ごとにマンガ作品数の合計を計算し、辞書として格納
# これにより、各マンガ雑誌で全体のマンガ作品数が分かる
mcname2ncc_total = df_pie3.groupby("マンガ雑誌名")["マンガ作品数"].sum().to_dict()

# 'アニメ化'がTrueのレコードをフィルタリングし、
# 各マンガ雑誌名の最初のマンガ作品数を計算して辞書として格納
# これにより、各マンガ雑誌でアニメ化されたマンガ作品数が分かる
mcname2ncc_anime = (
    df_pie3[df_pie3["アニメ化"]]
    .groupby("マンガ雑誌名")["マンガ作品数"]
    .first()
    .to_dict()
)

# 各マンガ雑誌名ごとにアニメ化率を計算し、辞書として格納
# アニメ化率はアニメ化されたマンガ作品数を全体のマンガ作品数で割ったもの
mcname2ratio = {k: v / mcname2ncc_total[k] for k, v in mcname2ncc_anime.items()}

# 'マンガ雑誌名'ごとにアニメ化率を'アニメ化率'列としてデータフレームに追加
df_pie3["アニメ化率"] = df_pie3["マンガ雑誌名"].map(mcname2ratio)

# データフレームを'アニメ化率'と'アニメ化'で昇順に並び替え、インデックスをリセット
# これにより、アニメ化率が低い雑誌から高い雑誌へと順に並ぶ
df_pie3 = df_pie3.sort_values(["アニメ化率", "アニメ化"], ignore_index=True)
Hide code cell content
# 可視化対象のDataFrameを確認
df_pie3.head()
マンガ雑誌名 アニメ化 マンガ作品数 アニメ化率
0 週刊少年チャンピオン False 381 0.028061
1 週刊少年チャンピオン True 11 0.028061
2 週刊少年サンデー False 255 0.111498
3 週刊少年サンデー True 32 0.111498
4 週刊少年マガジン False 284 0.115265
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_pie3, DIR_OUT, "pie3")
DataFrame is saved as '../../data/mix/output/09/props/pie3.csv'.
Hide code cell source
# 'マンガ作品数'を円グラフの各セクションの大きさに、'アニメ化'を各セクションの名前に使用
# 'facet_col'によって、マンガ雑誌名ごとに異なる円グラフを生成
fig = px.pie(
    df_pie3,
    values="マンガ作品数",
    names="アニメ化",
    facet_col="マンガ雑誌名",
    color_discrete_sequence=OKABE_ITO,
)

# レイアウトを更新して凡例のタイトルを'アニメ化'に設定
fig.update_layout(legend={"title": "アニメ化"})

# 各円グラフのタイトル(マンガ雑誌名)を、"マンガ雑誌名=値"から"値"のみに短縮
# これにより、グラフ上の各マンガ雑誌名の表示が簡潔になる
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# 作成した図を表示
show_fig(fig)

棒グラフ#

円グラフにて、「四大少年誌で連載されたマンガ作品のうちアニメ化されたものは1割程度である」が悪くない仮説であることがわかりました。 ここでは棒グラフ、特に積上げ棒グラフを用いて、雑誌別のアニメ化率を別の角度から可視化してみましょう。

積上げ棒グラフStacked Bar Chart ) とは、 新たな質的変数に応じて棒グラフの内訳を分割し、 直列に並べた 可視化手法でした。 詳細は7章に整理してありますので、復習に役立ててください。

Hide code cell content
# df_pie2データフレームのコピーを作成してdf_barに格納
df_bar = df_pie2.copy()

# 各マンガ雑誌名ごとに、マンガ作品数の比率を計算し、新たな列として追加
df_bar = add_ratio_by_col(df_bar, "マンガ作品数", "マンガ雑誌名")

# 計算した比率を文字列形式で整形し、「text」列として追加
df_bar = add_text_of_ratio(df_bar, "ratio")

# 「ratio」列の名前を「比率」に変更
# これにより、データフレームの列名がより直感的に理解しやすくなる
df_bar = df_bar.rename(columns={"ratio": "比率"})

# アニメ化率列を追加し、これに基づきソート
df_bar["アニメ化率"] = df_bar["マンガ雑誌名"].map(mcname2ratio)
df_bar = df_bar.sort_values(
    ["アニメ化率", "アニメ化"], ascending=[True, False], ignore_index=True
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_bar.head()
マンガ雑誌名 アニメ化 マンガ作品数 マンガ雑誌名_total 比率 text アニメ化率
0 週刊少年チャンピオン True 11 392 0.028061 0.03 0.028061
1 週刊少年チャンピオン False 381 392 0.971939 0.97 0.028061
2 週刊少年サンデー True 32 287 0.111498 0.11 0.111498
3 週刊少年サンデー False 255 287 0.888502 0.89 0.111498
4 週刊少年マガジン True 37 321 0.115265 0.12 0.115265
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_bar, DIR_OUT, "bar")
DataFrame is saved as '../../data/mix/output/09/props/bar.csv'.
Hide code cell source
# Plotly Expressを使用して、df_barデータフレームから棒グラフを作成
# 'マンガ雑誌名'をx軸に、'比率'をy軸に設定し、'アニメ化'によって色分け
# 'text'列の値を棒グラフ上にテキストとして表示
# 'barmode'で'stack'を指定することで積上げ棒グラフ化
# 色の配列をOKABE_ITOの最初の2色を逆順(アニメ化実績ありをオレンジにするため)で指定
fig = px.bar(
    df_bar,
    x="マンガ雑誌名",
    y="比率",
    barmode="stack",
    color="アニメ化",
    text="text",
    color_discrete_sequence=OKABE_ITO[:2][::-1],
)

# 作成した図を表示する関数を呼び出し
show_fig(fig)

上図は、各マンガ雑誌に掲載されたマンガ作品のアニメ化率を表現した積上げ棒グラフです。 なお、1990年以降に連載を開始し、8話以上継続したマンガ作品を可視化対象としています。 オレンジ色(True)がアニメ化実績のあるマンガ作品であり、黒色(False)がそうでない作品を表します。

円グラフでも確認しましたが、週刊少年ジャンプにおけるアニメ化率が最も高いことがわかります。 上図で示したのはあくまでも 作品数の比率であり、作品数数ではない ことに注意が必要です。

Hide code cell source
# Plotly Expressを使用して、df_barデータフレームから棒グラフを作成
# 'マンガ雑誌名'をx軸に、'マンガ作品数'をy軸に設定し、'アニメ化'によって色分け
# 'マンガ作品数'列の値を棒グラフ上にテキストとして表示
# 'barmode'で'stack'を指定することで積上げ棒グラフ化
# 色の配列をOKABE_ITOの最初の2色を逆順(アニメ化実績ありをオレンジにするため)で指定
fig = px.bar(
    df_bar,
    x="マンガ雑誌名",
    y="マンガ作品数",
    barmode="stack",
    color="アニメ化",
    text="マンガ作品数",
    color_discrete_sequence=OKABE_ITO[:2][::-1],
)

# 作成した図を表示する関数を呼び出し
show_fig(fig)

上図は、各マンガ雑誌に掲載されたもののうち、アニメ化された 作品数 とそうでない 作品数 を表現した積上げ棒グラフです。 なお、1990年以降に連載を開始し、8話以上継続したマンガ作品を可視化対象としています。 オレンジ色(True)がアニメ化実績のあるマンガ作品であり、黒色(False)がそうでない作品を表します。

可視化対象をマンガ作品数とすることで、そもそも週刊少年ジャンプ週刊少年チャンピオンにおけるマンガ作品数が多いことがわかります。 また、アニメ化した マンガ作品数 という観点でも週刊少年ジャンプが最も多いことがわかります。

なお、積上げ棒グラフで内訳を表現する場合は、合計値で並び替える方が見やすくなることもあります。

Hide code cell source
# Plotly Expressを使用して、df_barデータフレームから棒グラフを作成
# 事前にdf_barを'マンガ雑誌名_total'で並び替えておくことで視認性を高める
# 'マンガ雑誌名'をx軸に、'マンガ作品数'をy軸に設定し、'アニメ化'によって色分け
# 'マンガ作品数'列の値を棒グラフ上にテキストとして表示
# 'barmode'で'stack'を指定することで積上げ棒グラフ化
# 色の配列をOKABE_ITOの最初の2色を逆順(アニメ化実績ありをオレンジにするため)で指定
fig = px.bar(
    df_bar.sort_values("マンガ雑誌名_total"),
    x="マンガ雑誌名",
    y="マンガ作品数",
    barmode="stack",
    color="アニメ化",
    text="マンガ作品数",
    color_discrete_sequence=OKABE_ITO[:2][::-1],
)

# 作成した図を表示する関数を呼び出し
show_fig(fig)

上図は、各マンガ雑誌に掲載されたもののうち、アニメ化された 作品数 とそうでない 作品数 を表現した積上げ棒グラフです。 なお、1990年以降に連載を開始し、8話以上継続したマンガ作品を可視化対象としています。 オレンジ色(True)がアニメ化実績のあるマンガ作品であり、黒色(False)がそうでない作品を表します。 直前の図と異なり、合計マンガ作品数でマンガ雑誌を並び替えて表示しました。

伝えたいメッセージに応じて、棒の順序を変更することも有効だということを覚えておきましょう。

モザイクプロット#

積上げ棒グラフでは、アニメ化実績のあるマンガ作品の比率と絶対数をそれぞれ別のグラフとして可視化しました。 本項ではモザイクプロットを用いて、比率と絶対数を同時に表現する方法を紹介します。

モザイクプロットMosaic Plot ) とは、複数の質的変数に対して、その内訳を 長方形の面積 で表した可視化手法でした。 その見た目から マリメッコプロットMarimekko Plot) とも呼ばれます。 分割方法を工夫することで三変数以上にも対応可能ですが,よく見かけるのは二変数に対する描画です。

二変数に対するモザイクプロットは、 積上げ棒グラフ の棒の太さを分母の大きさで調整したものと捉えることができます。 これにより、二変数を跨いだ(他の棒中の要素との)比較が可能になりますが、目視で面積を測るのは難しい場合があるので数値を付記すると親切です. 詳細は7章に整理してありますので、復習に役立てましょう。

Hide code cell content
# df_barデータフレームのコピーを作成してdf_mosaicに格納
df_mosaic = df_bar.copy()

# 'アニメ化'列のデータ型を文字列型に変換
# (create_mosaicplotではブール値の列を扱えないため)
df_mosaic["アニメ化"] = df_mosaic["アニメ化"].astype(str)
Hide code cell content
# 可視化対象のDataFrameを表示
df_mosaic.head()
マンガ雑誌名 アニメ化 マンガ作品数 マンガ雑誌名_total 比率 text アニメ化率
0 週刊少年チャンピオン True 11 392 0.028061 0.03 0.028061
1 週刊少年チャンピオン False 381 392 0.971939 0.97 0.028061
2 週刊少年サンデー True 32 287 0.111498 0.11 0.111498
3 週刊少年サンデー False 255 287 0.888502 0.89 0.111498
4 週刊少年マガジン True 37 321 0.115265 0.12 0.115265
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_mosaic, DIR_OUT, "mosaic")
DataFrame is saved as '../../data/mix/output/09/props/mosaic.csv'.
Hide code cell source
# create_mosaicplot関数を使用して、df_mosaicデータフレームからモザイクプロットを作成
# モザイクプロットは、'マンガ雑誌名'を基準に、'比率'に基づいて各セグメントの大きさを決定
# 'アニメ化'によって色分けし、各セグメントの幅は'マンガ雑誌名_total'に基づく
# 'text'列の値を各セグメント上にテキストとして表示し、色の配列はOKABE_ITOの最初の2色を逆順で指定
fig = create_mosaicplot(
    df_mosaic,
    x="マンガ雑誌名",
    y="比率",
    color="アニメ化",
    width="マンガ雑誌名_total",
    text="text",
    color_discrete_sequence=OKABE_ITO[:2][::-1],
)

# 作成したモザイクプロットを表示する関数を呼び出し
show_fig(fig)

上図は、各マンガ雑誌に掲載されたマンガ作品のアニメ化率を表したモザイクプロットです。 1990年以降に連載を開始し、合計8話以上継続したマンガ作品を可視化対象としています。 オレンジ色(True)がアニメ化実績のあるマンガ作品であり、黒色(False)がそうでない作品を表します。 各マンガ雑誌の「棒」の太さはそのマンガ雑誌に掲載された合計マンガ作品数に比例しています。 以上から、長方形の面積は各分類のマンガ作品数と対応していることになります。

このように、モザイクプロットを用いることでマンガ作品数の比率と絶対数を同時に表現することができます。

積上げ密度プロット#

四大少年誌に掲載されたマンガ作品のうち1割程度がアニメ化されている可能性があることがわかりました。 では、連載開始時期とアニメ化実績の間になにか関係はあるのでしょうか? つまり、将来的にアニメ原作となるマンガ作品が集中して連載を開始していたり、あるいはその逆で、毎年1割程度がアニメ化に至るように(意図的かどうかは別として)均されていたりするのでしょうか?

全く根拠はありませんが、便宜上ここでは前者を仮説として設定します。 本項では、積上げ密度プロットを用いて「同一年に連載を開始したマンガ作品のうちアニメ化に至る作品の割合は、年によって大きく変動する」という仮説を確認します。

積上げ密度プロットStacked Density Plot ) は、質的変数の内訳の推移を 面積 で表現する可視化手法でした。 エリアプロットArea Plot )と呼ばれることもあります。横軸に質的変数(例えば日付)、縦軸に質的変数の内訳あるいは絶対量を取ります。 詳細は7章に整理してありますので、復習に役立ててください。

Hide code cell content
# `first_date_cc`列を日付型に変換して、その年だけを取り出し`first_year_cc`列として追加
df_cc_ac["first_year_cc"] = pd.to_datetime(df_cc_ac["first_date_cc"]).dt.year

# `first_year_cc`と`is_animated`でグループ化し
# ユニークな`ccid`の数をカウントして新しいデータフレームを作成
df_area = (
    df_cc_ac.groupby(["first_year_cc", "is_animated"])["ccid"]
    .nunique()
    .reset_index(name="マンガ作品数")
)

# `first_year_cc`と`is_animated`でソート、アニメ化されたものを優先して表示
df_area = df_area.sort_values(
    by=["first_year_cc", "is_animated"], ascending=[True, False], ignore_index=True
)

# `is_animated`列のデータタイプを文字列に変更
df_area["is_animated"] = df_area["is_animated"].astype(str)

# 各年ごとのマンガ作品数の割合を計算して列に追加
df_area = add_ratio_by_col(df_area, "マンガ作品数", "first_year_cc")

# 列名をわかりやすい名前に変更
df_area = df_area.rename(
    columns={
        "first_year_cc": "連載開始年",
        "is_animated": "アニメ化",
        "ratio": "比率",
        "n_cc": "マンガ作品数",
    }
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_area.head()
連載開始年 アニメ化 マンガ作品数 first_year_cc_total 比率
0 1990 True 4 50 0.080000
1 1990 False 46 50 0.920000
2 1991 True 3 51 0.058824
3 1991 False 48 51 0.941176
4 1992 True 1 45 0.022222
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_area, DIR_OUT, "area")
DataFrame is saved as '../../data/mix/output/09/props/area.csv'.
Hide code cell source
# 積上げ密度グラフを作成
# x軸には連載開始年、y軸には比率、色分けにはアニメ化の有無を使用
# カラースキームとしてOKABE_ITOパレットの最初の2色を逆順で使用
# (逆順にしたのは、アニメ化実績のある作品をオレンジ色にするため)
# ホバー時に表示するデータにマンガ作品数を追加
fig = px.area(
    df_area,
    x="連載開始年",
    y="比率",
    color="アニメ化",
    color_discrete_sequence=OKABE_ITO[:2][::-1],
    hover_data=["マンガ作品数"],
)

# ホバー時の挙動をとして'x unified'を設定
fig.update_layout(hovermode="x unified")

# グラフを表示
show_fig(fig)

上図は、四大少年誌に掲載されたマンガ作品に対し、アニメ化実績の有無の内訳をその連載開始年ごとに集計した積上げ密度プロットです。 1990年以降に連載を開始し、合計8話以上継続したマンガ作品を可視化対象としています。 オレンジ色がアニメ化実績のあるマンガ作品を、黒色がそうでない作品の割合を示します。

2015年以降に大きく下がっているのは、データの打ち切り[1]による影響が考えられるため一旦解釈を保留します。 2014年以前を見ても、アニメ化実績のある作品の割合は一定ではなく、連載開始年によって大きく上下していることがわかります。 「同一年に連載を開始したマンガ作品のうちアニメ化に至る作品の割合は、年によって大きく変動する」という仮説は、どうやら正しそうです。

ちなみに、アニメ化に至るマンガ作品の割合が最も高まったのは2003年です。 内訳を見てみましょう。

Hide code cell content
# 2003年に連載が開始され、かつアニメ化された作品をフィルタリングし、特定の列のみ表示
df_cc_ac[(df_cc_ac["first_year_cc"] == 2003) & (df_cc_ac["is_animated"])][
    ["mcname", "ccname", "acname"]
]
mcname ccname acname
623 週刊少年サンデー MAR メル MÄR メルヘヴン MÄRCHEN AWAKENS ROMANCE
629 週刊少年マガジン 魔法先生 ネギま! MAGISTER NEGI MAGI 魔法先生 ネギま!
637 週刊少年サンデー 結界師 結界師
640 週刊少年マガジン ツバサ ~RESERVoir CHRoNiCLE~ ツバサ・クロニクル 年代記[第1期]
644 週刊少年ジャンプ 武装錬金 武装錬金
652 週刊少年ジャンプ DEATH NOTE DEATH NOTE
663 週刊少年ジャンプ 家庭教師ヒットマンREBORN! 家庭教師* ヒットマン REBORN! *[かてきょー]
664 週刊少年マガジン 涼風 涼風[すずか]

では、比率ではなく絶対数を可視化してみましょう。

Hide code cell source
# 積上げ密度グラフを作成
# x軸には連載開始年、y軸にはマンガ作品数の割合、色分けにはアニメ化の有無を使用
# カラースキームとしてOKABE_ITOパレットの最初の2色を逆順で使用
# (逆順にしたのは、アニメ化実績のある作品をオレンジ色にするため)
# ホバー時に表示するデータに比率を追加
fig = px.area(
    df_area,
    x="連載開始年",
    y="マンガ作品数",
    color="アニメ化",
    color_discrete_sequence=OKABE_ITO[:2][::-1],
    hover_data=["比率"],
)

# ホバー時の挙動をとして'x unified'を設定
fig.update_layout(hovermode="x unified")

# グラフを表示
show_fig(fig)

上図は、四大少年誌に掲載されたマンガ作品に対し、アニメ化実績の有無をその連載開始年ごとに集計した積上げ密度プロットです。 1990年以降に連載を開始し、合計8話以上継続したマンガ作品を可視化対象としています。 オレンジ色がアニメ化実績のあるマンガ作品を、黒色がそうでないものを示します。

そもそもマンガ作品の連載開始年が大きく上下していることがわかります。 特に1998年と2010年は連載を開始したマンガ作品数が例年を大きく下回ります。

せっかくですので、マンガ雑誌別の傾向も見てみましょう。

Hide code cell content
# `mcname`, `first_year_cc`, `is_animated`でグループ化し、
# 各グループ内のユニークな`ccid`の数をカウントし、マンガ作品数列として追加
df_area2 = (
    df_cc_ac.groupby(["mcname", "first_year_cc", "is_animated"])["ccid"]
    .nunique()
    .reset_index(name="マンガ作品数")
)

# 結果を`mcname`, `first_year_cc`, `is_animated`の順にソート
# `mcname`と`first_year_cc`は昇順、`is_animated`は降順
df_area2 = df_area2.sort_values(
    by=["mcname", "first_year_cc", "is_animated"],
    ascending=[True, True, False],
    ignore_index=True,
)

# `is_animated`列のデータタイプをブール型から文字列型に変更
df_area2["is_animated"] = df_area2["is_animated"].astype(str)

# `mcname`と`first_year_cc`ごとに`n_cc`(マンガ作品数)の比率を計算し、新しい列として追加
df_area2 = add_ratio_by_col(df_area2, "マンガ作品数", ["mcname", "first_year_cc"])

# 列名をよりわかりやすく変更
df_area2 = df_area2.rename(
    columns={
        "mcname": "マンガ雑誌名",
        "first_year_cc": "連載開始年",
        "is_animated": "アニメ化",
        "n_cc": "マンガ作品数",
        "ratio": "比率",
    }
)
Hide code cell content
# 可視化対象のDataFrameを確認
df_area2.head()
マンガ雑誌名 連載開始年 アニメ化 マンガ作品数 ['mcname', 'first_year_cc']_total 比率
0 週刊少年サンデー 1990 True 1 12 0.083333
1 週刊少年サンデー 1990 False 11 12 0.916667
2 週刊少年サンデー 1991 True 1 10 0.100000
3 週刊少年サンデー 1991 False 9 10 0.900000
4 週刊少年サンデー 1992 False 14 14 1.000000
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_area2, DIR_OUT, "area2")
DataFrame is saved as '../../data/mix/output/09/props/area2.csv'.
Hide code cell source
# 積上げ密度プロットを作成
# x軸には連載開始年、y軸にはマンガ作品数、色分けにはアニメ化の有無を使用
# マンガ雑誌名ごとに異なるファセットを作成、ただし各ファセットは縦に並べる
# カラースキームには岡部伊都パレットの最初の2色を逆順で使用
fig = px.area(
    df_area2,
    x="連載開始年",
    y="マンガ作品数",
    color="アニメ化",
    facet_col="マンガ雑誌名",
    facet_col_wrap=1,
    color_discrete_sequence=OKABE_ITO[:2][::-1],
    height=600,
)

# ホバー時の挙動を設定。'x unified'は同じx値を持つデータポイントで情報を統一して表示
fig.update_layout(hovermode="x unified")

# 各ファセットのタイトル(マンガ雑誌名)を編集し、雑誌名のみを表示
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))

# グラフを表示
show_fig(fig)

上図は、各マンガ雑誌に掲載されたマンガ作品に対し、アニメ化実績の有無をその連載開始年ごとに集計した積上げ密度プロットです。 1990年以降に連載を開始し、合計8話以上継続したマンガ作品を可視化対象としています。 オレンジ色がアニメ化実績のあるマンガ作品を、黒色がそうでないものを示します。

マンガ雑誌ごとに特徴的な挙動をしています。 マンガが好きな方は、各連載開始年を構成するマンガ作品を確認してみましょう。

ツリーマップ#

マンガ雑誌ごとに、アニメ化に至るマンガ作品の割合に違いがあることをこれまで見てきました。 それでは、アニメ化されたあと、例えばアニメ作品ごとの各話数の分布に特徴はあるのでしょうか? 本項では、ツリーマップを用いて「アニメ作品ごとの各話数の分布は、原作マンガ雑誌ごとに異なる」という仮説を確認します。

ツリーマップTree Map) とは、 階層構造(ツリー構造)を持つ 質的変数に対して、その内訳を 長方形の面積 で表現する可視化手法でした。 詳細は7章に整理してありますので、復習に役立ててください。

Hide code cell content
# アニメ化実績のあるレコードのみを抽出
df_tree = df_cc_ac[df_cc_ac["is_animated"]].reset_index(drop=True)

# 可視化用に列名を変更
cols_tree = {"mcname": "マンガ雑誌名", "ccname": "マンガ作品名", "n_ae": "アニメ各話数"}
df_tree = format_cols(df_tree, cols_tree)
Hide code cell content
# 可視化対象のDataFrameを確認
df_tree.head()
マンガ雑誌名 マンガ作品名 アニメ各話数
0 週刊少年サンデー うしおととら 26.0
1 週刊少年マガジン シュート! 58.0
2 週刊少年ジャンプ SLAM DUNK 101.0
3 週刊少年ジャンプ 幽☆遊☆白書 112.0
4 週刊少年サンデー ゴーストスイーパー美神 極楽大作戦!! 45.0
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_tree, DIR_OUT, "tree")
DataFrame is saved as '../../data/mix/output/09/props/tree.csv'.
Hide code cell source
# ツリーマップを作成
# 連載開始が1990年以降のマンガ作品を原作とするアニメ作品をテーマに設定
# 最上位は固定のテキスト、次にマンガ雑誌名、最後にマンガ作品名を指定し、サイズを`n_ae`で定義
# カラースキームにはOKABE_ITOパレットを使用
fig = px.treemap(
    df_tree,
    path=[
        px.Constant("1990年以降連載開始のマンガ作品を原作とするアニメ作品"),
        "マンガ雑誌名",
        "マンガ作品名",
    ],
    values="アニメ各話数",
    color_discrete_sequence=OKABE_ITO,
)

# 背景色を薄いグレーに設定
fig.update_traces(root_color="lightgrey")

# 可視化結果を表示
show_fig(fig)

上図は、四大少年誌を原作とするアニメ作品の各話数を表現したツリーマップです。 原作マンガ作品が掲載されたマンガ雑誌を基準に配色しており、長方形の大きさはアニメ各話数と対応しています。 また、1990年以降に連載を開始し、8話以上継続したマンガ作品を原作とするアニメ作品を可視化対象としています。

ツリーマップを用いることで、複雑な階層構造を持つ質的変数の全体像を簡単に把握することができます。 まずアニメ各話数という観点では、週刊少年ジャンプが圧倒的に多く、週刊少年サンデー週刊少年マガジンそして週刊少年チャンピオンが続くことがわかります。 また、各マンガ雑誌内のアニメ各話数の分布も直感的に把握することが可能です。 例えば、週刊少年ジャンプを原作とするアニメ作品は非常に各話数が大きいものから小さいものまで混在していますが、週刊少年チャンピオンに関しては小規模なものが目立ちます。

一方で、ツリーマップは定量的な情報伝達を得意としていません。 例えば、週刊少年ジャンプテニスの王子様を原作とするアニメ各話数と、週刊少年サンデー犬夜叉を原作とするアニメ各話数はどちらが多いでしょうか? 筆者は、ツリーマップを探索的データ分析の初期段階で利用することが多く、より深い定量的な分析を行う際は他の可視化手法を検討します。

パラレルセットグラフ#

アニメ化に至ったマンガ作品とそうでない作品は、合計各話数に違いが生じるのでしょうか? 素直に考えると、人気作品ほど合計各話数が多くなりそうですし、そのぶんアニメ化の機会も多そうです。 また、アニメ化によって人気が高まり、合計各話数が増えるというパターンもあるかもしれません。

そこで、本項ではパラレルセットグラフを用いて「アニメ化実績のあるマンガ作品は合計各話数が多い」という仮説を確認します。

パラレルセットグラフParallel Set Graph ) とは、複数の質的変数に対して、それらの内訳を 平行棒の面積 (あるいは 長さ )で表現する可視化手法でした。 三つ以上の質的変数に対しても適用可能であるという利点があります。 パラレルセットグラフを作成する上でのポイントは、最も強調したい質的変数を左側に配置し、かつ色付けすることです。 詳細は7章に整理してありますので、復習に役立ててください。

Hide code cell content
# 各マンガ作品の各話数数(n_ce)の閾値リストを作成
# 最小値(min_nce)、四分位数(25%、50%、75%の値)、最大値+1をリストに含める
ths_ce = (
    [min_nce]
    + list(df_cc_ac["n_ce"].quantile([0.25, 0.5, 0.75]).astype(int))
    + [df_cc_ac["n_ce"].max() + 1]
)

# マンガ作品IDからグループ名とグループIDをマッピングするための辞書を初期化
ccid2gname = {}
ccid2gid = {}

# 閾値リストを用いてマンガ作品を異なるグループに分類
for i in range(len(ths_ce) - 1):
    lower = ths_ce[i]  # 現在の区間の下限値
    upper = ths_ce[i + 1]  # 現在の区間の上限値

    # n_ceが現在の閾値区間に含まれるマンガ作品をフィルタリング
    df_q = df_cc_ac[(df_cc_ac["n_ce"] >= lower) & (df_cc_ac["n_ce"] < upper)]

    # 現在の区間に基づいてグループ名を定義(例: "8-15話")
    gname = f"{lower}-{upper-1}話"
    # フィルタリングされたマンガ作品のIDごとにグループ名をマッピング
    ccid2gname.update({ccid: gname for ccid in df_q["ccid"]})
    # フィルタリングされたマンガ作品のIDごとにグループIDをマッピング
    ccid2gid.update({ccid: i for ccid in df_q["ccid"]})
Hide code cell content
# df_cc_acデータフレームのコピーを作成してdf_parに格納
df_par = df_cc_ac.copy()

# 'ccid'に基づいてマンガ作品を特定のグループ名にマッピングし、新しい列'gname'に格納
df_par["gname"] = df_par["ccid"].map(ccid2gname)
# 'ccid'に基づいてマンガ作品を特定のグループIDにマッピングし、新しい列'gid'に格納
df_par["gid"] = df_par["ccid"].map(ccid2gid)

# 'is_animated'列を整数型に変換し、新しい列'is_animated_int'に格納
# アニメ化されているかどうかの真偽値を整数値に変換する
# (パラレルセットグラフでは、色分け対象の列としてカテゴリカル値を指定できないため)
df_par["is_animated_int"] = df_par["is_animated"].astype(int)

# 'is_animated'列を文字列型に変換し、後の処理や表示を統一しやすくする
df_par["is_animated"] = df_par["is_animated"].astype(str)

# 'mcname'(マンガ雑誌名)、'is_animated'(アニメ化されているか)、'gid'(グループID)に基づいて
# データフレームを並び替え、新しいインデックスを割り当て
df_par = df_par.sort_values(["mcname", "is_animated", "gid"], ignore_index=True)

# データフレームの列名をより分かりやすい名称に変更
cols_par = {
    "ccid": "マンガ作品ID",
    "is_animated": "アニメ化",
    "is_animated_int": "アニメ化有無ID",
    "gname": "合計話数",
    "gid": "グループID",
    "mcname": "マンガ雑誌名",
}
df_par = format_cols(df_par, cols_par)
Hide code cell content
# 可視化対象のDataFrameを確認
df_par.head()
マンガ作品ID アニメ化 アニメ化有無ID 合計話数 グループID マンガ雑誌名
0 C92355 False 0 8-17話 0 週刊少年サンデー
1 C93126 False 0 8-17話 0 週刊少年サンデー
2 C91698 False 0 8-17話 0 週刊少年サンデー
3 C93467 False 0 8-17話 0 週刊少年サンデー
4 C93829 False 0 8-17話 0 週刊少年サンデー
Hide code cell content
# 可視化対象DataFrameを保存
save_df_to_csv(df_par, DIR_OUT, "par")
DataFrame is saved as '../../data/mix/output/09/props/par.csv'.
Hide code cell source
# Plotly Expressを使用して、df_parデータフレームからパラレルセットグラフを作成
# 'アニメ化'、'合計話数'、'マンガ雑誌名'を軸として使用
# 'is_animated_int'に基づいて、OKABE_ITOスケールで色分け
fig = px.parallel_categories(
    df_par,
    dimensions=["アニメ化", "合計話数", "マンガ雑誌名"],
    color="アニメ化有無ID",
    color_continuous_scale=OKABE_ITO[:2],
)

# 色のスケールバーを非表示に設定
fig.update_coloraxes(showscale=False)

# 作成した平行座標プロットを表示する関数を呼び出し
show_fig(fig)

上図は、四大少年誌に掲載されたマンガ作品を対象に、アニメ化に至ったか否か、合計話数のグループ、そしてマンガ雑誌の内訳を整理したパラレルセットグラフです。 1990年以降に連載を開始し、かつ8話以上継続したマンガ作品を可視化対象としています。 合計話数のグループ分けは四分位値を基準に行いました。 配色は、アニメ化に至った(True、オレンジ色)か否(False、黒色)を基準に行いました。

各列においてオレンジ色の棒に注目することで、アニメ化実績のあるマンガ作品の比率を確認できます。 一番左側の列(アニメ化)は、そもそもアニメ化実績のあるマンガ作品とそうでない作品の比率を表現しています。 何度も繰り返していますが、アニメ化に至る作品は一握りです。 中央の列(合計話数)は、四分位値を基準に分けたグループにおけるアニメ化作品数の比率を表しています。 アニメ化に至ったマンガ作品のほとんどは、合計話数が上位25%以上のグループ(93-1008話)に属していますが、そのグループの中でさえ三分の一程度の比率でしかないことがわかります。 一番右側の列(マンガ雑誌名)を見ると、各マンガ雑誌におけるアニメ化作品の比率を確認できます。 これまで何度も触れてきた内容ですので、詳細は割愛します。

以上から、「アニメ化実績のあるマンガ作品は合計各話数が多い」という仮説はあながち誤りではなさそう、ということがわかります。 しかしこれはいかなる因果関係も示していないことは、ここで再度強調させてください。

ちなみに、パラレルセットグラフでは列の配置を変更することで、データを見る観点を変えることができます。 合計話数のグループを一番左に配置することでこれを確かめてみましょう。

Hide code cell source
# Plotly Expressを使用して、df_parデータフレームからパラレルセットグラフを作成
# '合計話数'、'アニメ化'、'マンガ雑誌名'を軸として使用
# 'グループID'に基づいて、px.colors.diverging.Portlandで色分け
fig = px.parallel_categories(
    df_par,
    dimensions=["合計話数", "アニメ化", "マンガ雑誌名"],
    color="グループID",
    color_continuous_scale=px.colors.diverging.Portland,
)

# 色のスケールバーを非表示に設定
fig.update_coloraxes(showscale=False)

# 作成した平行座標プロットを表示する関数を呼び出し
show_fig(fig)

上図は、四大少年誌に掲載されたマンガ作品を対象に、合計話数のグループ、アニメ化に至ったか否か、そしてマンガ雑誌の内訳を整理したパラレルセットグラフです。 1990年以降に連載を開始し、かつ8話以上継続したマンガ作品を可視化対象としています。 合計話数のグループ分けは四分位値を基準に行いました。 配色は合計話数のグループに従っています。

合計話数のグループが、アニメ化実績有無やマンガ雑誌内でどのような比率で存在しているか、理解しやすくなりました。